﻿using System;
using System.IO;
using ooWii.IO;
using ooWii.Collections;

namespace veriify
{
	class Program
	{
		static void Main( string[] args )
		{
			DateTime start = DateTime.Now;

			try
			{
				if( args.Length == 0 )
				{
					process( "." );
				}
				else
				{
					for( int i = 0; i < args.Length; i++ )
					{
						try
						{
							if( !process( args[ i ] ) )
							{
								Console.Error.WriteLine( string.Format( "No Wii images match {0}", args[ i ] ) );
							}
						}
						catch( Exception ex )
						{
							Console.Error.WriteLine( string.Format( "{0} {1}", args[ i ], ex.Message ) );
						}
					}
				}
			}
			catch( Exception ex )
			{
				Console.Error.WriteLine( string.Format( "{0}", ex.Message ) );
			}

			Console.Out.WriteLine( string.Format( "Finished in {0}", DateTime.Now.Subtract( start ) ) );

			if( System.Diagnostics.Debugger.IsAttached )
			{
				Console.In.ReadLine();
			}
		}

		static bool process( string parameter )
		{
			DiscList discList = new DiscList( parameter );

			try
			{
				for( int i = 0; i < discList.Count; i++ )
				{
					IDiscInfo discInfo = discList[ i ];

					try
					{
						process( discInfo.OpenRead() );
					}
					catch( Exception ex )
					{
						Console.Error.WriteLine( string.Format( "{0} {1}", discInfo.Location, ex.Message ) );
					}
				}

				return discList.Count > 0;
			}
			finally
			{
				discList.Dispose();
			}
		}

		static int progressLength;
		static bool partitionOk;
		const int maxNameLength = 40;

		static void outputProgress( string s )
		{
			string clear = s.PadRight( progressLength ) + "\r";
			Console.Out.Write( clear );

			progressLength = s.Length;
		}

		static void clearOutput()
		{
			outputProgress( string.Empty );
		}

		static void outputProgress( WiiPartition wiiPartition, string s )
		{
			if( s.Length > maxNameLength )
			{
				s = "..." + s.Substring( s.Length - maxNameLength + 3 );
			}

			outputProgress( string.Format( "{0:x16} {1:x8} {2}", wiiPartition.Ticket.TitleId, wiiPartition.Type, s ) );
		}

		static void outputError( WiiPartition wiiPartition, string message )
		{
			partitionOk = false;
			clearOutput();
			Console.Error.WriteLine( string.Format( "{0:x16} {1:x8} {2}", wiiPartition.Ticket.TitleId, wiiPartition.Type, message ) );
		}

		static void process( Stream iso )
		{
			try
			{
				WiiDiscHeader header = new WiiDiscHeader( iso );

				Console.Out.WriteLine( string.Format( "{0}{1}{2}{3} {4}", header.DiscId, header.GameCode, header.RegionCode, header.MakerCode, header.GameTitle ) );

				WiiPartitionList partitions = new WiiPartitionList( iso );

				for( int i = 0; i < partitions.Count; i++ )
				{
					WiiPartition wiiPartition = partitions[ i ];

					partitionOk = true;

					outputProgress( wiiPartition, "Verifying signatures" );

					try
					{
						wiiPartition.CertificateChain.VerifySignature();
						wiiPartition.Ticket.VerifySignature( wiiPartition.CertificateChain );
						wiiPartition.Tmd.VerifySignature( wiiPartition.CertificateChain );
						wiiPartition.VerifyHash();
					}
					catch( Exception ex )
					{
						outputError( wiiPartition, ex.Message );
					}

					WiiPartitionStream stream = wiiPartition.OpenRead();
					GCDiscHeader partitionHeader = new GCDiscHeader( stream );

					processDirectory( wiiPartition, partitionHeader.RootDirectory );

					clearOutput();

					if( partitionOk )
					{
						Console.Out.WriteLine( string.Format( "{0:x16} {1:x8} OK", wiiPartition.Ticket.TitleId, wiiPartition.Type ) );
					}
				}

				Console.Out.WriteLine();
				partitions.Dispose();
			}
			finally
			{
				clearOutput();
				iso.Close();
			}
		}

		static void processDirectory( WiiPartition wiiPartition, GCDirectoryInfo directoryInfo )
		{
			GCFileInfo[] fileInfos = directoryInfo.GetFiles();
			for( int i = 0; i < fileInfos.Length; i++ )
			{
				processFile( wiiPartition, fileInfos[ i ] );
			}

			GCDirectoryInfo[] directoryInfos = directoryInfo.GetDirectories();
			for( int i = 0; i < directoryInfos.Length; i++ )
			{
				processDirectory( wiiPartition, directoryInfos[ i ] );
			}
		}

		static void processFile( WiiPartition wiiPartition, GCFileInfo fileInfo )
		{
			outputProgress( wiiPartition, fileInfo.FullName );

			Stream inputStream = fileInfo.OpenRead();

			try
			{
				long length = 0;

				byte[] buffer = new byte[ 32768 ];

				while( true )
				{
					try
					{
						int read = inputStream.Read( buffer, 0, buffer.Length );
						if( read < 1 )
						{
							break;
						}

						length += read;
					}
					catch( Exception ex )
					{
						outputError( wiiPartition, string.Format( "{0} failed after {1} of {2} bytes, {3}", fileInfo.FullName, length, inputStream.Length, ex.Message ) );
						return;
					}
				}

				if( inputStream.Length != length )
				{
					outputError( wiiPartition, string.Format( "{0} expected {1} bytes but read {2}", fileInfo.FullName, inputStream.Length, length ) );
				}
			}
			finally
			{
				inputStream.Close();
			}
		}
	}
}
